This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.

Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Ctrl+Shift+Enter.

library(osmdata)
Warning: package ‘osmdata’ was built under R version 4.2.1
Data (c) OpenStreetMap contributors, ODbL 1.0. https://www.openstreetmap.org/copyright
library(sf)
Warning: package ‘sf’ was built under R version 4.2.1
Linking to GEOS 3.9.1, GDAL 3.4.3, PROJ 7.2.1; sf_use_s2() is TRUE
library(ggplot2)
Warning: package ‘ggplot2’ was built under R version 4.2.1
library(plotly)
Warning: package ‘plotly’ was built under R version 4.2.1
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: ‘plotly’

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
library(rgeos)
Warning: package ‘rgeos’ was built under R version 4.2.1
Loading required package: sp
Warning: package ‘sp’ was built under R version 4.2.1
rgeos version: 0.5-9, (SVN revision 684)
 GEOS runtime version: 3.9.1-CAPI-1.14.2 
 Please note that rgeos will be retired by the end of 2023,
plan transition to sf functions using GEOS at your earliest convenience.
 GEOS using OverlayNG
 Linking to sp version: 1.5-0 
 Polygon checking: TRUE 
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────────────────────────────────────────────────────── tidyverse 1.3.1 ──
✔ tibble  3.1.7     ✔ dplyr   1.0.9
✔ tidyr   1.2.0     ✔ stringr 1.4.0
✔ readr   2.1.2     ✔ forcats 0.5.1
✔ purrr   0.3.4     
── Conflicts ────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks plotly::filter(), stats::filter()
✖ dplyr::lag()    masks stats::lag()
get_overpass_url()
[1] "https://overpass.kumi.systems/api/interpreter"

FYI all osmdata object can be combined using the c() operator. This c() operation can only be performed on simple feature (sf) type geographic features. I.e. you have to use osmdata_sf() to create the simple feature or convert sp type features to sf type features. I think that calling the bbox based on the county perimeter is better. There are some campuses that are not labeled only “University of Georgia” explicity, so it may be best to use st_… methods to get landuse polygons based on the UGA operator key and then use those polygons to filter and select UGA buildings. I could also use the trim_osmdata() function to select from the list of all UGA operated landuse polygons and run through them, one-by-one and obtain a building list that way. I need to use format_out = “polygon” in the getbb() function in order for this to work. There may be some edge-cases (e.g. building is on UGA campus landuse polygon, yet is not a UGA operated building.) that lead me to think that simply adding operator=“University of Georgia” to each building is worth the effort.

Pro-tip: value_exact needs to be false in order for match_case to also be false and not fail the query.

bb_county <- getbb('Athens-Clarke County')
all_Buildings <- opq(bbox = bb_county) %>%
  add_osm_feature(key = "building") %>%
  add_osm_feature(key = "operator", value = "University of Georgia", value_exact = FALSE, match_case = FALSE) %>%
  osmdata_sf()
ugaCampus <- opq(bbox = bb_county) %>%
  add_osm_feature(key = "landuse", value = "property") %>%
  add_osm_feature(key = "name", value = "University of Georgia", value_exact = FALSE, match_case = FALSE) %>%
  osmdata_sf()
#Create sf dataset of the UGA campus polygons.
ugaCampus <- ugaCampus$osm_polygons

This query allows me to access the multipolygon that comprises the UGA campus. I will then use this to create a bbox based on this multipolygon. I need to also filter out only the polygons from this multipolygon that are explicitly labeled as universities (i.e. amenity = university).

ugaCampus <- ugaCampus %>% filter(amenity == "university")

Get all bicycle parking features in Athens. There are, I believe, at least two bicycle parking features mapped as polygons, so in order for a completely accurate bike parking count, I need to include them. It may cause some issues downstream.

all_Bicycle_Nodes <- opq(bbox =bb_county) %>%
  add_osm_feature(key = "amenity", value = "bicycle_parking") %>%
  osmdata_sf()
all_Bicycle_Nodes <- all_Bicycle_Nodes$osm_points

Filter only bicycle nodes within the UGA campus.

mat = st_intersects(all_Bicycle_Nodes, ugaCampus, sparse = FALSE)

mat <- apply(mat, 1, any)

all_Bicycle_Nodes_Trimmed <- all_Bicycle_Nodes[mat,]

Export the bicycles nodes and UGA buildings as shapefiles.

st_write(all_Buildings,
         "UGA_buildings.shp",
         layer_options = "ENCODING=UTF-8",
         delete_layer = TRUE)
Warning in abbreviate_shapefile_names(obj) :
  Field names abbreviated for ESRI Shapefile driver
Deleting layer `UGA_buildings' using driver `ESRI Shapefile'
Writing layer `UGA_buildings' to data source `UGA_buildings.shp' using driver `ESRI Shapefile'
options:        ENCODING=UTF-8 
Writing 437 features with 68 fields and geometry type Polygon.
st_write(all_Bicycle_Nodes_Trimmed,
         "Bicycle_parking.shp",
         layer_options = "ENCODING=UTF-8",
         delete_layer = TRUE)
Warning in abbreviate_shapefile_names(obj) :
  Field names abbreviated for ESRI Shapefile driver
Deleting layer `Bicycle_parking' using driver `ESRI Shapefile'
Writing layer `Bicycle_parking' to data source `Bicycle_parking.shp' using driver `ESRI Shapefile'
options:        ENCODING=UTF-8 
Writing 211 features with 22 fields and geometry type Point.

Combine the UGA bicycle parking and buildings datasets.

building_Bike_Combined_Data <- c(all_Buildings, all_Bicycle_Nodes_Trimmed)

ggplotly interactive map of UGA buildings and bike racks.

q <- ggplot() + geom_sf(data = building_Bike_Combined_Data$osm_points,
  fill = 'light blue') + theme_minimal() + geom_sf(data = building_Bike_Combined_Data$osm_polygons,
  fill = 'light blue') + theme_minimal()
ggplotly()

b is building c is bike nodes. Think code is only able to do simple polygons at the time. I will need to add multi-polygon ability in the future.

b <- building_Bike_Combined_Data$osm_polygons
c <- building_Bike_Combined_Data$osm_points
##  First project data into a planar coordinate system (here EPSG:2240)
utmStr <- "+proj=EPSG:2240 +zone=%d +datum=WGS84 +units=ft +no_defs +ellps=GRS80"
crs <- st_crs(2240)

#Perform spatial transform on bike nodes and building polygons
pUTM <- st_transform(b, crs)
ptsUTM <- st_transform(c, crs)
## Set up containers for results
n <- nrow(ptsUTM)
nearestBuilding <- character(n)
distToNearestBuilding <- numeric(n)
buildingCapacity <- numeric(n)
buildingCount <- numeric(n)
buildingRackType <- character(n)
buildingRackCovered <- character(n)

The st_nearest_feature function may also be really useful here. I think I would like to add a column to the original unprojected simple feature type.

## For each point, find name of nearest polygon (in this case, UGA buildings)
for (i in seq_along(nearestBuilding)) {
    buildingCapacity <- ptsUTM$capacity[i,]
    gDists <- st_distance(ptsUTM[i,], pUTM, byid=TRUE)
    nearestBuilding[i] <- pUTM$name[which.min(gDists)]
    distToNearestBuilding[i] <- min(gDists)
}
## Check that it worked
nearestDistanceName <- data.frame(nearestBuilding, distToNearestBuilding)

#E.g. should return something like this.
#       nearestCanton distToNearestCanton
# 1             Wiltz           15342.222
# 2        Echternach            7470.728
# 3            Remich           20520.800
# 4          Clervaux            6658.167
# 5        Echternach           22177.771
# 6          Clervaux           26388.388
# 7           Redange            8135.764
# 8            Remich            2199.394
# 9  Esch-sur-Alzette           11776.534
# 10           Remich           14998.204

plot(c, pch=16, col="red")
#text(c, 1:10, pos=3)
plot(b, add=TRUE)
text(b, b$name, cex=0.7)

Write the dataframe to a .csv file.

write(nearestDistanceName, "test.dbf")
Error in cat(x, file = file, sep = c(rep.int(sep, ncolumns - 1), "\n"),  : 
  argument 1 (type 'list') cannot be handled by 'cat'

When you save the notebook, an HTML file containing the code and output will be saved alongside it (click the Preview button or press Ctrl+Shift+K to preview the HTML file).

The preview shows you a rendered HTML copy of the contents of the editor. Consequently, unlike Knit, Preview does not run any R code chunks. Instead, the output of the chunk when it was last run in the editor is displayed.

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpUaGlzIGlzIGFuIFtSIE1hcmtkb3duXShodHRwOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tKSBOb3RlYm9vay4gV2hlbiB5b3UgZXhlY3V0ZSBjb2RlIHdpdGhpbiB0aGUgbm90ZWJvb2ssIHRoZSByZXN1bHRzIGFwcGVhciBiZW5lYXRoIHRoZSBjb2RlLiANCg0KVHJ5IGV4ZWN1dGluZyB0aGlzIGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqUnVuKiBidXR0b24gd2l0aGluIHRoZSBjaHVuayBvciBieSBwbGFjaW5nIHlvdXIgY3Vyc29yIGluc2lkZSBpdCBhbmQgcHJlc3NpbmcgKkN0cmwrU2hpZnQrRW50ZXIqLiANCg0KYGBge3J9DQpsaWJyYXJ5KG9zbWRhdGEpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KHJnZW9zKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KYGBge3J9DQpnZXRfb3ZlcnBhc3NfdXJsKCkNCmBgYA0KRllJIGFsbCBvc21kYXRhIG9iamVjdCBjYW4gYmUgY29tYmluZWQgdXNpbmcgdGhlIGMoKSBvcGVyYXRvci4gVGhpcyBjKCkgb3BlcmF0aW9uIGNhbiBvbmx5IGJlIHBlcmZvcm1lZCBvbiBzaW1wbGUgZmVhdHVyZSAoc2YpIHR5cGUgZ2VvZ3JhcGhpYyBmZWF0dXJlcy4gSS5lLiB5b3UgaGF2ZSB0byB1c2Ugb3NtZGF0YV9zZigpIHRvIGNyZWF0ZSB0aGUgc2ltcGxlIGZlYXR1cmUgb3IgY29udmVydCBzcCB0eXBlIGZlYXR1cmVzIHRvIHNmIHR5cGUgZmVhdHVyZXMuIEkgdGhpbmsgdGhhdCBjYWxsaW5nIHRoZSBiYm94IGJhc2VkIG9uIHRoZSBjb3VudHkgcGVyaW1ldGVyIGlzIGJldHRlci4gVGhlcmUgYXJlIHNvbWUgY2FtcHVzZXMgdGhhdCBhcmUgbm90IGxhYmVsZWQgb25seSAiVW5pdmVyc2l0eSBvZiBHZW9yZ2lhIiBleHBsaWNpdHksIHNvIGl0IG1heSBiZSBiZXN0IHRvIHVzZSBzdF8uLi4gbWV0aG9kcyB0byBnZXQgbGFuZHVzZSBwb2x5Z29ucyBiYXNlZCBvbiB0aGUgVUdBIG9wZXJhdG9yIGtleSBhbmQgdGhlbiB1c2UgdGhvc2UgcG9seWdvbnMgdG8gZmlsdGVyIGFuZCBzZWxlY3QgVUdBIGJ1aWxkaW5ncy4gSSBjb3VsZCBhbHNvIHVzZSB0aGUgdHJpbV9vc21kYXRhKCkgZnVuY3Rpb24gdG8gc2VsZWN0IGZyb20gdGhlIGxpc3Qgb2YgYWxsIFVHQSBvcGVyYXRlZCBsYW5kdXNlIHBvbHlnb25zIGFuZCBydW4gdGhyb3VnaCB0aGVtLCBvbmUtYnktb25lIGFuZCBvYnRhaW4gYSBidWlsZGluZyBsaXN0IHRoYXQgd2F5LiBJIG5lZWQgdG8gdXNlIGZvcm1hdF9vdXQgPSAicG9seWdvbiIgaW4gdGhlIGdldGJiKCkgZnVuY3Rpb24gaW4gb3JkZXIgZm9yIHRoaXMgdG8gd29yay4gVGhlcmUgbWF5IGJlIHNvbWUgZWRnZS1jYXNlcyAoZS5nLiBidWlsZGluZyBpcyBvbiBVR0EgY2FtcHVzIGxhbmR1c2UgcG9seWdvbiwgeWV0IGlzIG5vdCBhIFVHQSBvcGVyYXRlZCBidWlsZGluZy4pIHRoYXQgbGVhZCBtZSB0byB0aGluayB0aGF0IHNpbXBseSBhZGRpbmcgb3BlcmF0b3I9IlVuaXZlcnNpdHkgb2YgR2VvcmdpYSIgdG8gZWFjaCBidWlsZGluZyBpcyB3b3J0aCB0aGUgZWZmb3J0Lg0KDQpQcm8tdGlwOiB2YWx1ZV9leGFjdCBuZWVkcyB0byBiZSBmYWxzZSBpbiBvcmRlciBmb3IgbWF0Y2hfY2FzZSB0byBhbHNvIGJlIGZhbHNlIGFuZCBub3QgZmFpbCB0aGUgcXVlcnkuDQoNCmBgYHtyfQ0KYmJfY291bnR5IDwtIGdldGJiKCdBdGhlbnMtQ2xhcmtlIENvdW50eScpDQphbGxfQnVpbGRpbmdzIDwtIG9wcShiYm94ID0gYmJfY291bnR5KSAlPiUNCiAgYWRkX29zbV9mZWF0dXJlKGtleSA9ICJidWlsZGluZyIpICU+JQ0KICBhZGRfb3NtX2ZlYXR1cmUoa2V5ID0gIm9wZXJhdG9yIiwgdmFsdWUgPSAiVW5pdmVyc2l0eSBvZiBHZW9yZ2lhIiwgdmFsdWVfZXhhY3QgPSBGQUxTRSwgbWF0Y2hfY2FzZSA9IEZBTFNFKSAlPiUNCiAgb3NtZGF0YV9zZigpDQphbGxfQnVpbGRpbmdzIDwtIGFsbF9CdWlsZGluZ3Mkb3NtX3BvbHlnb25zDQpgYGANCg0KYGBge3J9DQp1Z2FDYW1wdXMgPC0gb3BxKGJib3ggPSBiYl9jb3VudHkpICU+JQ0KICBhZGRfb3NtX2ZlYXR1cmUoa2V5ID0gImxhbmR1c2UiLCB2YWx1ZSA9ICJwcm9wZXJ0eSIpICU+JQ0KICBhZGRfb3NtX2ZlYXR1cmUoa2V5ID0gIm5hbWUiLCB2YWx1ZSA9ICJVbml2ZXJzaXR5IG9mIEdlb3JnaWEiLCB2YWx1ZV9leGFjdCA9IEZBTFNFLCBtYXRjaF9jYXNlID0gRkFMU0UpICU+JQ0KICBvc21kYXRhX3NmKCkNCiNDcmVhdGUgc2YgZGF0YXNldCBvZiB0aGUgVUdBIGNhbXB1cyBwb2x5Z29ucy4NCnVnYUNhbXB1cyA8LSB1Z2FDYW1wdXMkb3NtX3BvbHlnb25zDQpgYGANCg0KVGhpcyBxdWVyeSBhbGxvd3MgbWUgdG8gYWNjZXNzIHRoZSBtdWx0aXBvbHlnb24gdGhhdCBjb21wcmlzZXMgdGhlIFVHQSBjYW1wdXMuIEkgd2lsbCB0aGVuIHVzZSB0aGlzIHRvIGNyZWF0ZSBhIGJib3ggYmFzZWQgb24gdGhpcyBtdWx0aXBvbHlnb24uIEkgbmVlZCB0byBhbHNvIGZpbHRlciBvdXQgb25seSB0aGUgcG9seWdvbnMgZnJvbSB0aGlzIG11bHRpcG9seWdvbiB0aGF0IGFyZSBleHBsaWNpdGx5IGxhYmVsZWQgYXMgdW5pdmVyc2l0aWVzIChpLmUuIGFtZW5pdHkgPSB1bml2ZXJzaXR5KS4NCg0KYGBge3J9DQp1Z2FDYW1wdXMgPC0gdWdhQ2FtcHVzICU+JSBmaWx0ZXIoYW1lbml0eSA9PSAidW5pdmVyc2l0eSIpDQpgYGANCg0KDQpHZXQgYWxsIGJpY3ljbGUgcGFya2luZyBmZWF0dXJlcyBpbiBBdGhlbnMuIFRoZXJlIGFyZSwgSSBiZWxpZXZlLCBhdCBsZWFzdCB0d28gIGJpY3ljbGUgcGFya2luZyBmZWF0dXJlcyBtYXBwZWQgYXMgcG9seWdvbnMsIHNvIGluIG9yZGVyIGZvciBhIGNvbXBsZXRlbHkgYWNjdXJhdGUgYmlrZSBwYXJraW5nIGNvdW50LCBJIG5lZWQgdG8gaW5jbHVkZSB0aGVtLiBJdCBtYXkgY2F1c2Ugc29tZSBpc3N1ZXMgZG93bnN0cmVhbS4NCg0KYGBge3J9DQphbGxfQmljeWNsZV9Ob2RlcyA8LSBvcHEoYmJveCA9YmJfY291bnR5KSAlPiUNCiAgYWRkX29zbV9mZWF0dXJlKGtleSA9ICJhbWVuaXR5IiwgdmFsdWUgPSAiYmljeWNsZV9wYXJraW5nIikgJT4lDQogIG9zbWRhdGFfc2YoKQ0KYWxsX0JpY3ljbGVfTm9kZXMgPC0gYWxsX0JpY3ljbGVfTm9kZXMkb3NtX3BvaW50cw0KYGBgDQoNCkZpbHRlciBvbmx5IGJpY3ljbGUgbm9kZXMgd2l0aGluIHRoZSBVR0EgY2FtcHVzLg0KDQpgYGB7cn0NCm1hdCA9IHN0X2ludGVyc2VjdHMoYWxsX0JpY3ljbGVfTm9kZXMsIHVnYUNhbXB1cywgc3BhcnNlID0gRkFMU0UpDQoNCm1hdCA8LSBhcHBseShtYXQsIDEsIGFueSkNCg0KYWxsX0JpY3ljbGVfTm9kZXNfVHJpbW1lZCA8LSBhbGxfQmljeWNsZV9Ob2Rlc1ttYXQsXQ0KYGBgDQoNCkV4cG9ydCB0aGUgYmljeWNsZXMgbm9kZXMgYW5kIFVHQSBidWlsZGluZ3MgYXMgc2hhcGVmaWxlcy4NCg0KYGBge3J9DQpzdF93cml0ZShhbGxfQnVpbGRpbmdzLA0KICAgICAgICAgIlVHQV9idWlsZGluZ3Muc2hwIiwNCiAgICAgICAgIGxheWVyX29wdGlvbnMgPSAiRU5DT0RJTkc9VVRGLTgiLA0KICAgICAgICAgZGVsZXRlX2xheWVyID0gVFJVRSkNCg0Kc3Rfd3JpdGUoYWxsX0JpY3ljbGVfTm9kZXNfVHJpbW1lZCwNCiAgICAgICAgICJCaWN5Y2xlX3Bhcmtpbmcuc2hwIiwNCiAgICAgICAgIGxheWVyX29wdGlvbnMgPSAiRU5DT0RJTkc9VVRGLTgiLA0KICAgICAgICAgZGVsZXRlX2xheWVyID0gVFJVRSkNCmBgYA0KDQoNCkNvbWJpbmUgdGhlIFVHQSBiaWN5Y2xlIHBhcmtpbmcgYW5kIGJ1aWxkaW5ncyBkYXRhc2V0cy4NCg0KYGBge3J9DQpidWlsZGluZ19CaWtlX0NvbWJpbmVkX0RhdGEgPC0gYyhhbGxfQnVpbGRpbmdzLCBhbGxfQmljeWNsZV9Ob2Rlc19UcmltbWVkKQ0KYGBgDQoNCmdncGxvdGx5IGludGVyYWN0aXZlIG1hcCBvZiBVR0EgYnVpbGRpbmdzIGFuZCBiaWtlIHJhY2tzLg0KDQpgYGB7cn0NCnEgPC0gZ2dwbG90KCkgKyBnZW9tX3NmKGRhdGEgPSBidWlsZGluZ19CaWtlX0NvbWJpbmVkX0RhdGEkb3NtX3BvaW50cywNCiAgZmlsbCA9ICdsaWdodCBibHVlJykgKyB0aGVtZV9taW5pbWFsKCkgKyBnZW9tX3NmKGRhdGEgPSBidWlsZGluZ19CaWtlX0NvbWJpbmVkX0RhdGEkb3NtX3BvbHlnb25zLA0KICBmaWxsID0gJ2xpZ2h0IGJsdWUnKSArIHRoZW1lX21pbmltYWwoKQ0KZ2dwbG90bHkoKQ0KYGBgDQpiIGlzIGJ1aWxkaW5nIGMgaXMgYmlrZSBub2Rlcy4gVGhpbmsgY29kZSBpcyBvbmx5IGFibGUgdG8gZG8gc2ltcGxlIHBvbHlnb25zIGF0IHRoZSB0aW1lLiBJIHdpbGwgbmVlZCB0byBhZGQgbXVsdGktcG9seWdvbiBhYmlsaXR5IGluIHRoZSBmdXR1cmUuDQoNCmBgYHtyfQ0KYiA8LSBidWlsZGluZ19CaWtlX0NvbWJpbmVkX0RhdGEkb3NtX3BvbHlnb25zDQpjIDwtIGJ1aWxkaW5nX0Jpa2VfQ29tYmluZWRfRGF0YSRvc21fcG9pbnRzDQpgYGANCg0KYGBge3J9DQojIyAgRmlyc3QgcHJvamVjdCBkYXRhIGludG8gYSBwbGFuYXIgY29vcmRpbmF0ZSBzeXN0ZW0gKGhlcmUgRVBTRzoyMjQwKQ0KdXRtU3RyIDwtICIrcHJvaj1FUFNHOjIyNDAgK3pvbmU9JWQgK2RhdHVtPVdHUzg0ICt1bml0cz1mdCArbm9fZGVmcyArZWxscHM9R1JTODAiDQpjcnMgPC0gc3RfY3JzKDIyNDApDQoNCiNQZXJmb3JtIHNwYXRpYWwgdHJhbnNmb3JtIG9uIGJpa2Ugbm9kZXMgYW5kIGJ1aWxkaW5nIHBvbHlnb25zDQpwVVRNIDwtIHN0X3RyYW5zZm9ybShiLCBjcnMpDQpwdHNVVE0gPC0gc3RfdHJhbnNmb3JtKGMsIGNycykNCmBgYA0KDQoNCmBgYHtyfQ0KIyMgU2V0IHVwIGNvbnRhaW5lcnMgZm9yIHJlc3VsdHMNCm4gPC0gbnJvdyhwdHNVVE0pDQpuZWFyZXN0QnVpbGRpbmcgPC0gY2hhcmFjdGVyKG4pDQpkaXN0VG9OZWFyZXN0QnVpbGRpbmcgPC0gbnVtZXJpYyhuKQ0KYnVpbGRpbmdDYXBhY2l0eSA8LSBudW1lcmljKG4pDQpidWlsZGluZ0NvdW50IDwtIG51bWVyaWMobikNCmJ1aWxkaW5nUmFja1R5cGUgPC0gY2hhcmFjdGVyKG4pDQpidWlsZGluZ1JhY2tDb3ZlcmVkIDwtIGNoYXJhY3RlcihuKQ0KDQpgYGANCg0KVGhlIHN0X25lYXJlc3RfZmVhdHVyZSBmdW5jdGlvbiBtYXkgYWxzbyBiZSByZWFsbHkgdXNlZnVsIGhlcmUuIEkgdGhpbmsgSSB3b3VsZCBsaWtlIHRvIGFkZCBhIGNvbHVtbiB0byB0aGUgb3JpZ2luYWwgdW5wcm9qZWN0ZWQgc2ltcGxlIGZlYXR1cmUgdHlwZS4gDQpgYGB7cn0NCiMjIEZvciBlYWNoIHBvaW50LCBmaW5kIG5hbWUgb2YgbmVhcmVzdCBwb2x5Z29uIChpbiB0aGlzIGNhc2UsIFVHQSBidWlsZGluZ3MpDQpmb3IgKGkgaW4gc2VxX2Fsb25nKG5lYXJlc3RCdWlsZGluZykpIHsNCiAgICBidWlsZGluZ0NhcGFjaXR5IDwtIHB0c1VUTSRjYXBhY2l0eVtpLF0NCiAgICBnRGlzdHMgPC0gc3RfZGlzdGFuY2UocHRzVVRNW2ksXSwgcFVUTSwgYnlpZD1UUlVFKQ0KICAgIG5lYXJlc3RCdWlsZGluZ1tpXSA8LSBwVVRNJG5hbWVbd2hpY2gubWluKGdEaXN0cyldDQogICAgZGlzdFRvTmVhcmVzdEJ1aWxkaW5nW2ldIDwtIG1pbihnRGlzdHMpDQp9DQpgYGANCmBgYHtyfQ0KIyMgQ2hlY2sgdGhhdCBpdCB3b3JrZWQNCm5lYXJlc3REaXN0YW5jZU5hbWUgPC0gZGF0YS5mcmFtZShuZWFyZXN0QnVpbGRpbmcsIGRpc3RUb05lYXJlc3RCdWlsZGluZykNCg0KI0UuZy4gc2hvdWxkIHJldHVybiBzb21ldGhpbmcgbGlrZSB0aGlzLg0KIyAgICAgICBuZWFyZXN0Q2FudG9uIGRpc3RUb05lYXJlc3RDYW50b24NCiMgMSAgICAgICAgICAgICBXaWx0eiAgICAgICAgICAgMTUzNDIuMjIyDQojIDIgICAgICAgIEVjaHRlcm5hY2ggICAgICAgICAgICA3NDcwLjcyOA0KIyAzICAgICAgICAgICAgUmVtaWNoICAgICAgICAgICAyMDUyMC44MDANCiMgNCAgICAgICAgICBDbGVydmF1eCAgICAgICAgICAgIDY2NTguMTY3DQojIDUgICAgICAgIEVjaHRlcm5hY2ggICAgICAgICAgIDIyMTc3Ljc3MQ0KIyA2ICAgICAgICAgIENsZXJ2YXV4ICAgICAgICAgICAyNjM4OC4zODgNCiMgNyAgICAgICAgICAgUmVkYW5nZSAgICAgICAgICAgIDgxMzUuNzY0DQojIDggICAgICAgICAgICBSZW1pY2ggICAgICAgICAgICAyMTk5LjM5NA0KIyA5ICBFc2NoLXN1ci1BbHpldHRlICAgICAgICAgICAxMTc3Ni41MzQNCiMgMTAgICAgICAgICAgIFJlbWljaCAgICAgICAgICAgMTQ5OTguMjA0DQoNCnBsb3QoYywgcGNoPTE2LCBjb2w9InJlZCIpDQojdGV4dChjLCAxOjEwLCBwb3M9MykNCnBsb3QoYiwgYWRkPVRSVUUpDQp0ZXh0KGIsIGIkbmFtZSwgY2V4PTAuNykNCmBgYA0KDQpXcml0ZSB0aGUgZGF0YWZyYW1lIHRvIGEgLmNzdiBmaWxlLg0KDQpgYGB7cn0NCndyaXRlLmNzdihuZWFyZXN0RGlzdGFuY2VOYW1lLCAiVW5maW5pc2hlZF9uZWFyZXN0X2J1aWxkaW5nLmNzdiIpDQpgYGANCg0KDQpXaGVuIHlvdSBzYXZlIHRoZSBub3RlYm9vaywgYW4gSFRNTCBmaWxlIGNvbnRhaW5pbmcgdGhlIGNvZGUgYW5kIG91dHB1dCB3aWxsIGJlIHNhdmVkIGFsb25nc2lkZSBpdCAoY2xpY2sgdGhlICpQcmV2aWV3KiBidXR0b24gb3IgcHJlc3MgKkN0cmwrU2hpZnQrSyogdG8gcHJldmlldyB0aGUgSFRNTCBmaWxlKS4NCg0KVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUgZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZSBjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZSBlZGl0b3IgaXMgZGlzcGxheWVkLg0K